#include "RayTracer.h"

namespace RayTracing
{
	void RayTracer::setScene( Scene* _scene )
	{
		current_scene = _scene;
	}

	void RayTracer::renderScene()
	{
		clearFrameBuffer();

		// Axis range is defined by the min value - max value
		float x_axis_range = image_w;
		float y_axis_range = image_h;

		Geometry::Point p_screen;
		Geometry::Vector q;
		Geometry::Ray r;
		Properties::Color pixelColor;

		for( int y_coord = 0 ; y_coord < image_h ; y_coord++ )
		{
			for( int x_coord = 0 ; x_coord < image_w ; x_coord++ )
			{
				float x = (       (float)x_coord / (float)image_w )   * ( x_axis_range ) - ( x_axis_range / 2.0 );
				float y = ( 1 - ( (float)y_coord / (float)image_h ) ) * ( y_axis_range ) - ( y_axis_range / 2.0 );

				p_screen( x , y , 0.0 );

				q = p_screen - current_scene->getEye();

				r( current_scene->getEye(), q.unit() );

				pixelColor = rayTrace( r , 0 );

				setFramebuffer (x_coord, y_coord, pixelColor.r(), pixelColor.g(), pixelColor.b() );
			}
		}

	}

	Properties::Color RayTracer::rayTrace( const Geometry::Ray& r , int depth )
	{
		Geometry::Point closestObjectIntersection;

		Geometry::Object* closestObject = findClosestObject( r, 
			                                                 current_scene->objectList, 
															 closestObjectIntersection );

		Properties::Color totalColor;

		//The ray intersected an object in the scene
		if( closestObject )
		{
			Properties::Ambient closestObjectLighting_ambient  ( closestObject->lighting().a() );
			Properties::Specular closestObjectLighting_specular( closestObject->lighting().s() );
			Properties::Diffuse closestObjectLighting_diffuse  ( closestObject->lighting().d() );

			Properties::Color ka_A( closestObjectLighting_ambient.r() * current_scene->ambient.r(),
									closestObjectLighting_ambient.g() * current_scene->ambient.g() ,
									closestObjectLighting_ambient.b() * current_scene->ambient.b() );

			totalColor = totalColor + ka_A;

			//Cast rays from the Intersection Point to each light source
			for( unsigned int i = 0 ; i < current_scene->lightList.size() ; i++ )
			{
				RayTracing::Light* currentLight = current_scene->lightList[i];
				Properties::Color& currentLight_color = currentLight->c();

				Geometry::Vector v = currentLight->loc() - closestObjectIntersection;
				Geometry::Ray shadowRay( closestObjectIntersection , v.unit() );

				Geometry::Point blockingObjectInterPoint;

				Geometry::Object* blockingObject = findClosestObject( shadowRay, 
					                                                  current_scene->objectList,  
													                  blockingObjectInterPoint );

				//No blocking object found, so light is reaching this point.
				if( blockingObject == NULL )
				{
					//Diffuse Lighting Contribution
					float L_dot_N = shadowRay * closestObject->getNormal( closestObjectIntersection );

					Properties::Color diffuse_component( currentLight_color.r() * closestObjectLighting_diffuse.r() * L_dot_N ,
														 currentLight_color.g() * closestObjectLighting_diffuse.g() * L_dot_N ,
														 currentLight_color.b() * closestObjectLighting_diffuse.b() * L_dot_N );		

					totalColor = totalColor + diffuse_component;


					//Specular Lighting Contribution
					Geometry::Vector E( current_scene->getEye() - closestObjectIntersection );
					Geometry::Vector R( closestObject->getReflectedRay( shadowRay, closestObjectIntersection ).q() );

					float R_dot_E = abs( R.unit() * E.unit() );
					float exp_factor = pow(R_dot_E, 10 );

					Properties::Color specular_component( currentLight_color.r() * closestObjectLighting_specular.r() * exp_factor , 
														  currentLight_color.g() * closestObjectLighting_specular.g() * exp_factor , 
														  currentLight_color.b() * closestObjectLighting_specular.b() * exp_factor );

					totalColor = totalColor + specular_component;

				}

			}

			//Cast reflection rays
			if( depth < maxDepth )
			{
				Geometry::Ray incidentRay( closestObjectIntersection ,  -r.q()  );
				Geometry::Ray reflectedRay = closestObject->getReflectedRay( incidentRay , closestObjectIntersection );

				totalColor = totalColor + closestObjectLighting_specular * rayTrace( reflectedRay , depth + 1 );
			}	

		}

		return totalColor;
	}

	Geometry::Object* RayTracer::findClosestObject( const Geometry::Ray& _source , const std::vector<Geometry::Object*>& _objectList , Geometry::Point& inter_point )
	{
		float t_min = 1000;
		float t_current = 0.0;

		Geometry::Object * _closestObject = NULL;

		for( unsigned int i = 0 ; i < _objectList.size() ; i++ )
		{
			Geometry::Point intersection;

			if( _objectList[i]->intersect( _source , intersection , t_current ) )
			{
				if( t_current < t_min )
				{
					t_min = t_current;
					inter_point = intersection;
					_closestObject = _objectList[i];
				}
			}
		}

		return _closestObject;
	}

	void RayTracer::clearFrameBuffer()
	{
		int i,j;

		for(i=0;i<image_h;i++) {
			for (j=0;j<image_w;j++) {
				frameBuffer[i][j][0] = 0.0;
				frameBuffer[i][j][1] = 0.0;
				frameBuffer[i][j][2] = 0.0;
			}
		}
	}

	void RayTracer::setFramebuffer(int x, int y, float R, float G, float B)
	{
		// changes the origin from the lower-left corner to the upper-left corner
		y = image_h - 1 - y;
		if (R<=1.0)
			if (R>=0.0)
				frameBuffer[y][x][0]=R;
			else
				frameBuffer[y][x][0]=0.0;
		else
			frameBuffer[y][x][0]=1.0;
		if (G<=1.0)
			if (G>=0.0)
				frameBuffer[y][x][1]=G;
			else
				frameBuffer[y][x][1]=0.0;
		else
			frameBuffer[y][x][1]=1.0;
		if (B<=1.0)
			if (B>=0.0)
				frameBuffer[y][x][2]=B;
			else
				frameBuffer[y][x][2]=0.0;
		else
			frameBuffer[y][x][2]=1.0;
	}

}